﻿<!DOCTYPE html>
<html style="height:100%">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="referrer" content="always">
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"> <!--兼容手机不缩小-->
    <meta http-equiv="x-dns-prefetch-control" content="on">
    <meta name="description" content="光华智能AGV云平台、致力于自动储运新境界。">
    <meta name="format-detection" content="telephone=no">
    <title>光华智能AGV实时画面</title>
    <link rel="stylesheet" type="text/css" href="Content/Site2.css">
    <link rel="stylesheet" type="text/css" href="Content/Real.css">
    <script src="Scripts/jquery-3.4.1.min.js"></script>
    <!--<script src="Scripts/jquery.cookie1.4.1.min.js"></script>-->
    <script src="Scripts/jcanvas.min.js"></script>
    <!--<script src="Scripts/jcanvas.js"></script>-->
    <script src="Scripts/pad.js"></script>
    <script type="text/javascript">
        // 运行周期
        var intervar = setInterval(function () {
            getData();
        }, 100);

        // 标准颜色
        var colors = [
            '#e41c1c',
            '#e4981c',
            '#dae41c',
            '#9de41c',
            '#1eee9a',
            '#1cabe4',
            '#421ce4',
            '#7b1ce4',
            '#be1ce4',
            '#e41c93',
        ];

        /***********************************************************************
            @date	20230716
            @brief  AGV类
            @note
        ***********************************************************************/
        var hCars = {
            type: 'line',
            layer: true,
            fillStyle: '#000a',
            strokeStyle: '#589',
            strokeWidth: 100,
            rounded: true,
            closed: true,
            translateX: 0,
            translateY: 0,
            rotate: 0,
            x1: 1500, y1: 300,
            x2: 1500, y2: -300,
            x3: 1300, y3: -500,
            x4: -100, y4: -500,
            x5: -100, y5: -300,
            x6: -1100, y6: -300,
            x7: -1100, y7: -200,
            x8: -100, y8: -200,
            x9: -100, y9: 200,
            x10: -1100, y10: 200,
            x11: -1100, y11: 300,
            x12: -100, y12: 300,
            x13: -100, y13: 500,
            x14: 1300, y14: 500,

            data: {                             // 附加属性
                seek: -1,                       // 运行步，临时用
                roadAngle: 0,                   // 当前路段角度
                roadLength: 0,                  // 当前路段长度
                runDistance: 0,                 // AGV距起点距离
                carLength: 3000,                // AGV长度
            },

            // 画AGV
            draw: function () {
                // https://www.bootcdn.cn/jcanvas/
                // 地图转换到画布
                var cv = $(".cavAGV");
                translate(cv);

                var les = hMap.lines;

                // 添加图层
                var layers = cv.getLayers();
                if (!layers.length) {
                    // 创建AGV图层
                    for (var i = 0; i < 8; i++) {
                        hCars.data.seek = i;
                        hCars.name = 'agv' + i;
                        var ic = i % colors.length;
                        hCars.strokeStyle = colors[ic];
                        cv.addLayer(hCars);

                        cv.drawText({
                            layer: true,
                            name: 'agvnum' + i,
                            fillStyle: colors[ic],
                            //strokeStyle: '#25a',
                            //strokeWidth: hMap.scale,
                            x: 150, y: 100,
                            fontSize: 9 * hMap.scale,
                            fontFamily: 'Calibri',
                            text: i,
                        });
                    }
                }

                // ???? 临时用，以后从服务器传过来
                for (var i = 0; i < 8; i++) {
                    var layer = cv.getLayer('agv' + i);
                    var layertxt = cv.getLayer('agvnum' + i);

                    // 计算当前路段角度
                    if (layer.data.runDistance >= layer.data.roadLength) {
                        var seek = layer.data.seek + 1;
                        if (seek >= 8) {
                            seek = 0;
                        }
                        layer.data.seek = seek;
                        var x1 = les[seek][2];
                        var y1 = les[seek][3];
                        var x2 = les[seek + 1][2];
                        var y2 = les[seek + 1][3];
                        var dx = x2 - x1;
                        var dy = y2 - y1;
                        layer.data.roadAngle = toDegree(Math.atan2(dy, dx));
                        layer.data.roadLength = Math.sqrt(dx * dx + dy * dy);
                        layer.data.runDistance = 0;
                    }

                    // 相对当前道路角度，如果相同则前行，否则调整角度
                    var curAng = addAngle(layer.data.roadAngle, -layer.rotate);
                    var fd = 3;
                    var rotate = 0;
                    if (!curAng || EQUAL0(curAng, fd)) {
                        layer.data.runDistance += 200;
                        rotate = layer.data.roadAngle;
                    }
                    else {
                        rotate = (curAng > 0 ? fd : -fd);
                        rotate += layer.rotate;
                    }

                    var seek = layer.data.seek;
                    layer.x = les[seek][2];
                    layer.y = les[seek][3] + 1 * hMap.scale;         // 加一点，对齐AGV中线
                    layer.rotate = rotate;
                    layer.translateX = layer.data.runDistance;

                    layertxt.x = layer.x;
                    layertxt.y = layer.y;
                    layertxt.rotate = rotate;
                    layertxt.translateX = layer.data.runDistance + 700;
                }

                // 画AGV
                cv.drawLayers();
                // 复位画布参数
                //cv.restoreCanvas();
            },
        };

        // 登录成功
        function logged() {
            // 显示画布
            $(".cavGround").show();
            $(".cavAGV").show();
            $(".cavCtrl").show();
        }

        // 发送请求，处理返回数据
        var tim0;                                           // 时间节点
        var promptTxt = '';                                 // 正常时提示内容
        function getData() {

            // 刷新数据
            $.ajax({
                url: './PadRequest.ashx',
                type: 'post',
                data: {
                    id: 4,          // 请求数据ID
                    name: 'Real'    // 请求数据名称
                },

                // 成功返回数据
                success: function (res) {
                    // 未计算
                    if (hMap.scale <= 0) {
                        //$('#prompt').text("Loading ...");
                        return;
                    }

                    // 几何参数计算完成
                    try {
                        jsn = $.parseJSON(res);                 // 返回数据转换成JAON格式

                        if (jsn.Error == 1) {                   // 返回错误标识
                            $('#prompt').text(jsn.str);
                        }
                        else {
                            if (1 == hMap.redraw) {             // 背景只画一次
                                hMap.draw();
                                hDirection.draw();
                                hMap.redraw = 0;
                            }
                            hCars.draw();                       // 实时画AGV
                        }

                        $('#prompt').text(promptTxt);           // 正常，不显示
                    } catch (e) {
                        $('#prompt').text('返回数据格式错误！' + res);
                    }

                    var tim = new Date().getTime();
                    var interval = tim - tim0;
                    tim0 = tim;
                    $('#interval').text(interval);
                },
                error: function () {
                    $('#prompt').text('响应错误！');
                },
            });
        }

        // 画布坐标转换
        function translate(cv) {
            // 清除全部图形
            cv.clearCanvas();

            // 还原画布坐标，实测不起作用，加一行强制还原
            // 放到画图的最前面，避免有时抖动
            cv.restoreCanvas();
            // 因为上一行不起作用，这里强制还原
            var ctx = cv[0].getContext("2d");
            ctx.restore();

            // 重置画面坐标
            cv.scaleCanvas({
                autosave: true,             // 第一个填真，后面填假，否则不正常
                scale: 1 / hMap.scale,
                //x: 0, y: 0,
            });
            cv.translateCanvas({
                autosave: false,
                translateX: hMap.translateX,
                translateY: hMap.translateY,     // 新坐标单位，相对新坐标
            })
            cv.rotateCanvas({
                autosave: false,
                rotate: hMap.rotate,          // 角度
                //x: hMap.scaleX,
                //y: hMap.scaleY,
            });
        }

        // 角度弧度转换
        function toRadian(angle) {
            return angle * Math.PI / 180;
        }
        function toDegree(rad) {
            return rad * 180 / Math.PI;
        }

        /***********************************************************************
            @date	20230716
            @brief  弧度相加
            @param	nRad1 弧度1
            @param	nRad2 弧度2
            @retval	弧度和，调整到正负PI内，包括正负PI
            @note
        ***********************************************************************/
        function addRad(fRad1, fRad2) {
            var fResult = fRad1 + fRad2;

            while (fResult > PIF)
                fResult -= PIF * 2;

            while (fResult < -PIF)
                fResult += PIF * 2;

            return fResult;
        }

        /***********************************************************************
            @date	20230716
            @brief  角度相加
            @param	nAng1 角度1
            @param	nAng2 角度2
            @retval	角度和，调整到正负180内，包括正负180
            @note
        ***********************************************************************/
        function addAngle(fAng1, fAng2) {
            var fResult = fAng1 + fAng2;

            while (fResult > 180)
                fResult -= 360;

            while (fResult < -180)
                fResult += 360;

            return fResult;
        }

        // 比较大小
        // x 原始数据，y 被比较数据，z 允许误差
        // 相等 1，否则 0
        function EQUAL(x, y, z) {
            return (x > y ? x - y : y - x) <= z;
        }

        function EQUAL0(x, z) {
            return (x < z) && (x > -z);
        }

    </script>
</head>
<body style="height:calc(100%)">

    <div class="split2"></div>

    <div class="canva">
        <canvas class="cavGround"><!--地图--></canvas>
        <canvas class="cavAGV">><!--AGV--></canvas>
        <canvas class="cavCtrl">><!--按钮--></canvas>

        <div class="zhouqi">
            <div>周期：</div><div id="interval"></div>
            <div id="prompt"></div>
        </div>
        <div class="dirction"><!--方向按钮--></div>
    </div>

    <!--对话框-->
    <!--菜单-->
    <div id="menustation" class="dlgground">
        <div class="dialog">
            <input type="button" id="stationClear" value="清空" />
            <input type="button" id="stationFill" value="充满" />
            <input type="button" id="stationTrayType" value="选择托类型" />
            <input type="button" id="stationTray" value="设置托数" />
            <input type="button" id="stationOffset" value="设置偏移" />
            <input type="button" id="stationGap" value="间距和层数" />
            <input type="button" id="stationActive" value="激活" />
            <input type="button" id="stationRemove" value="停用" />
            <input type="button" id="stationClose" value="关闭" />
        </div>
    </div>

</body>
</html>

<script type="text/javascript">

    /***********************************************************************
        @date	20230716
        @brief  地图类
        @note
    ***********************************************************************/
    var hMap = {
        jcv: $('.cavGround'),       // 画布
        scale: 1,                   // 地图全局比例
        translateX: 0,              // 地图平移X
        translateY: 0,              // 地图平移Y
        rotate: 0,                  // 地图旋转角度
        redraw: 0,                  // 1 重画地图

        radian: 0,
        sin: 0,
        cos: 0,
        setAngle: function (ang) {
            this.rotate = ang;
            this.radian = toRadian(ang);
            this.sin = Math.sin(this.radian);
            this.cos = Math.cos(this.radian);
        },

        lines: [    // {序号，类型，X， Y], 序号为零表求线段开始
            [1, 1, -20000, 0],
            [2, 1, -15000, 10000],
            [3, 0, 0, 0, 15000],
            [4, 0, 15000, 10000],
            [5, 1, 20000, 0],
            [6, 0, 15000, -10000],
            [7, 1, 0, -15000],
            [8, 0, -15000, -10000],
            [9, 0, -20000, 0],
        ],

        line: {
            strokeStyle: '#ffff',
            strokeWidth: this.scale,
            //rounded: true,
            //closed: false,
            //x1: 0,
            //y1: 0,
            //x2: 10000,
            //y2: 10000,
            //rotate: -8,
        },

        draw: function () {
            var les = this.lines;
            var le = this.line;
            var cv = this.jcv;

            translate(cv);

            le.strokeWidth = this.scale;

            //var ple = $.extend( true, this.line );          // 复制对像
            //ple.strokeWidth = this.scale;
            var nm = 0;
            for (var i = les.length - 1; i >= 0; i--) {
                var n = les[i][0];
                if (nm === 0)
                    nm = n;
                le['x' + n] = les[i][2];
                le['y' + n] = les[i][3];
                if (n === 1) {
                    cv.drawLine(le);

                    for (var j = nm; j > 0; j--) {
                        delete le['x' + j];
                        delete le['y' + j];
                    }
                    nm = 0;
                }
            }
        },

        // https://www.bootcdn.cn/jcanvas/
        resize: function () {
            // 求区域
            var si = this.sin;
            var co = this.cos;
            var les = this.lines;

            var mapMinX = 1E38;                     // 单位mm
            var mapMaxX = -1E38;
            var mapMinY = 1E38;
            var mapMaxY = -1E38;
            var carlen = hCars.data.carLength;

            for (var i = les.length - 1; i >= 0; i--) {
                var x1 = les[i][2];
                var y1 = les[i][3];
                var x3 = co * x1 - si * y1;
                var y3 = co * y1 + si * x1;
                if (x3 < mapMinX)
                    mapMinX = x3;
                if (y3 < mapMinY)
                    mapMinY = y3;
                if (x3 > mapMaxX)
                    mapMaxX = x3;
                if (y3 > mapMaxY)
                    mapMaxY = y3;
            }

            var width = this.jcv.width();
            var height = this.jcv.height();
            $("canvas").attr("width", width);       // 必须这样设置画布的宽和高
            $("canvas").attr("height", height);

            // 居中
            var scaleX = (mapMaxX - mapMinX + carlen) / width;
            var scaleY = (mapMaxY - mapMinY + carlen) / height;
            if (scaleX < scaleY) {
                this.scale = scaleY;
                this.translateX = -(mapMaxX - mapMinX - width * this.scale) / 2 - mapMinX;
                this.translateY = -mapMinY + carlen / 2;
            }
            else {
                this.scale = scaleX;
                this.translateX = -mapMinX + carlen / 2;
                this.translateY = -(mapMaxY - mapMinY - height * this.scale) / 2 - mapMinY;
            }

            // 重画标识
            this.redraw = 1;
            //drawMap();   // 不知道为什么，不能直接在这里画
        }
    };
    hMap.setAngle(0);       // 设置初始角度、弧度、正弦、余弦
    $(window).resize(
        function () {
            hMap.resize();
        }
    );

    /***********************************************************************
        @date	20230716
        @brief  触控事件和左上角方向标识类
        @note
    ***********************************************************************/
    var hDirection = {
        jcv: $('.cavCtrl'),       // 画布

        touchX0: null,
        touchY0: null,
        touchA0: null,
        touchL0: null,  // touchX0为null时开始滚动页面，touchA0 为 null 时不进缩放状态
        translateX0: null,
        translateY0: null,

        addLayer: function () {
            // 创建方向箭头图层
            this.jcv.addLayer({
                type: 'line',
                name: 'dri_arrow',
                groups: ['driction'],
                fillStyle: '#f808',
                closed: true,
                x: 0, y: 25,
                x1: 0, y1: 0,
                x2: -6, y2: 15,
                x3: 0, y3: -15,
                x4: 6, y4: 15,
            });

            // 创建方向圆圈图层
            this.jcv.addLayer({
                type: 'arc',
                name: 'dri_cycle',
                groups: ['driction'],
                strokeStyle: '#f808',
                strokeWidth: 1,
                cursors: {
                    // 显示悬停时的指点器
                    mouseover: 'pointer',
                    // 显示鼠标按下时的'move'指针
                    //mousedown: 'move',
                    // 在鼠标弹起时反转指针
                    //mouseup: 'pointer'
                },
                x: 0, y: 25,
                radius: 15,
                mouseup: function (layer) {
                    // 当方形被点击时，运行这些代码
                    if ((hMap.rotate % 45) == 0)
                        hMap.setAngle(addAngle(hMap.rotate, 45));
                    else
                        hMap.setAngle(Math.round(hMap.rotate / 45) * 45);

                    hMap.resize();
                },
            });
        },

        draw: function () {
            // 方向标识
            var cv = this.jcv;
            var scale = hMap.scale;
            var si = hMap.sin;
            var co = hMap.cos;
            var dx = cv.width() - 25 - hMap.translateX / scale;
            var dy = 25 - hMap.translateY / scale;
            cv.setLayerGroup('driction', {
                scale: scale,
                translateX: dx * co + dy * si,
                translateY: dy * co - dx * si,
            });

            translate(cv);
            cv.drawLayers();
        },

        // 多点触控
        touchMove: function (e) {
            var offs = this.jcv.offset();
            var offsX = offs.left;
            var offsY = offs.top;

            switch (e.targetTouches.length) {
                case 1:
                    this.touchA0 = null;     // 禁止缩放

                    var Y0 = e.targetTouches[0].pageY - offsY;
                    if (this.touchY0 == null)
                        this.touchY0 = Y0;

                    // 单指触摸时滚动页面
                    window.scrollBy(0, (this.touchY0 - Y0) / 4);
                    break;

                case 2:
                    {
                        var X0 = e.targetTouches[0].pageX - offsX;
                        var Y0 = e.targetTouches[0].pageY - offsY;
                        var X1 = e.targetTouches[1].pageX - offsX;
                        var Y1 = e.targetTouches[1].pageY - offsY;

                        var dy = Y1 - Y0;
                        var dx = X1 - X0;
                        var touchA = toDegree(Math.atan2(dy, dx));
                        var touchL = Math.sqrt(dy * dy + dx * dx);

                        var touchX = (X0 + X1) / 2 * hMap.scale;     //得出鼠标在容器内的坐标X，以容器的左上角为坐标原点;
                        var touchY = (Y0 + Y1) / 2 * hMap.scale;

                        // 触控，不进行适应性计算
                        if (this.touchA0 != null) {
                            // 旋转角度
                            var angle = addAngle(touchA, -this.touchA0);
                            var tRad = toRadian(angle);
                            hMap.setAngle(addAngle(hMap.rotate, angle));
                            // 旋转中心，相对地图
                            var CX0 = this.touchX0 - hMap.translateX;
                            var CY0 = this.touchY0 - hMap.translateY;
                            var sint = Math.sin(tRad);
                            var cost = Math.cos(tRad);
                            // 旋转位移
                            var mx = CX0 * cost - CY0 * sint - CX0;
                            var my = CY0 * cost + CX0 * sint - CY0;
                            // 移动位移
                            mx -= touchX - this.touchX0;
                            my -= touchY - this.touchY0;
                            hMap.translateX -= mx;
                            hMap.translateY -= my;

                            // 缩放
                            hMap.scale /= (touchL / this.touchL0);

                            //promptTxt = parseInt(hMap.translateX / hMap.scale) + ', ' + parseInt(hMap.translateY / hMap.scale);
                            //promptTxt = parseInt(CY0 / hMap.scale);

                            // 方向标识
                            //this.draw();
                        }

                        // 保存新数据
                        this.touchA0 = touchA;
                        this.touchL0 = touchL;
                        this.touchX0 = touchX;
                        this.touchY0 = touchY;

                        //promptTxt = parseInt(CX0) + ', ' + parseInt(CY0);

                        hMap.redraw = 1;    // 重画
                        break;
                    }

                default:
                    return;
            }
        },

        // 触控结束
        touchEnd: function (e) {
            this.touchX0 = null;
            this.touchY0 = null;        // 滚动页面
            this.touchA0 = null;        // 结束缩放
            this.touchL0 = null;
        },

        // 鼠标控制
        mouseDown: function (e) {
            if (e.which !== 1)
                return;     // 只处理左键
            var offs = this.jcv.offset();
            var X = e.pageX - this.jcv.offset().left;
            var Y = e.pageY - this.jcv.offset().top;

            // 保存新数据
            this.touchA0 = hMap.rotate;
            this.touchL0 = hMap.scale;                  // 初始比例
            this.touchX0 = X * hMap.scale;
            this.touchY0 = Y * hMap.scale;
            this.translateX0 = hMap.translateX;
            this.translateY0 = hMap.translateY;

            promptTxt = this.jcv.offset().top + ', ' + this.jcv.offset().left;
        },
        mouseMove: function (e) {
            if (e.which !== 1)
                return;                                             // 只处理左键

            var offs = this.jcv.offset();
            var X = e.pageX - this.jcv.offset().left;
            var Y = e.pageY - this.jcv.offset().top;

            var dx = X - this.touchX0 / this.touchL0;         // 计算鼠标在容器内的坐标X，以容器的左上角为坐标原点;
            var dy = Y - this.touchY0 / this.touchL0;

            if (e.ctrlKey) {                                        // 缩放
                var scl = (dx - dy) / 200;              // 用两个方向的和计算，y方向相反
                if (scl > 0) {
                    scl += 1;
                }
                else {
                    scl = 1 / (1 - scl);
                }
                hMap.scale = this.touchL0 / scl;

                hMap.translateX = this.translateX0 + this.touchX0 / scl - this.touchX0;
                hMap.translateY = this.translateY0 + this.touchY0 / scl - this.touchY0;
            }
            else if (e.shiftKey) {                                        // 旋转
                var ang = (dx - dy) / 6;              // 用两个方向的和计算，y方向相反
                hMap.setAngle(addAngle(this.touchA0, -ang));

                //hMap.translateX = this.translateX0 + this.touchX0 / scl - this.touchX0;
                //hMap.translateY = this.translateY0 + this.touchY0 / scl - this.touchY0;
            }
            else {                                                  // 平移
                hMap.translateX = this.translateX0 + X * this.touchL0 - this.touchX0;
                hMap.translateY = this.translateY0 + Y * this.touchL0 - this.touchY0;
            }

            //promptTxt = parseInt(CX0) + ', ' + parseInt(CY0);

            hMap.redraw = 1;    // 重画
        },
    };
    hDirection.addLayer();

    // 放到最后才能正常显示（移动端）
    // $(window).load(onresize);不能正常显示
    hMap.resize();


    /***********************************************************************
        @date	20230716
        @brief  站点
        @note
    ***********************************************************************/
    var hStation = {
        jcv: $('.cavCtrl'),       // 画布

        // 这里只需要加层，画布坐标转换和重画在hDirection中完成
        addLayer: function () {
            var les = hMap.lines;
            var cv = this.jcv;

            for (var i = les.length - 1; i >= 0; i--) {
                if (les[i][1] === 0)
                    continue;

                // 添加站点圆圈层
                cv.addLayer({
                    type: 'arc',
                    name: 'station' + i,
                    groups: ['stations'],
                    //strokeStyle: '#fff8',
                    //strokeWidth: hMap.scale,
                    fillStyle: '#fff8',
                    cursors: {
                        // 显示悬停时的指点器
                        mouseover: 'pointer',
                        // 显示鼠标按下时的'move'指针
                        //mousedown: 'move',
                        // 在鼠标弹起时反转指针
                        //mouseup: 'pointer'
                    },
                    x: les[i][2], y: les[i][3],
                    radius: 500,
                    mouseup: function (layer) {
                        $("#menustation").show();;
                    },
                });
            };

            // 当方形被点击时，运行这些代码
            // 关闭菜单
            $("#stationClose").click(function () {
                $("#menustation").hide();     // show()方法对应display:block; 或  hide()方法对应display:none;
            });
        },
    }
    hStation.addLayer();


    /*********************************************************************************
      触摸和鼠标事件
    *********************************************************************************/
    var cv = $('.cavCtrl');
    cv.bind('touchmove', function (e) {
        hDirection.touchMove(e);
    });

    cv.bind('touchend', function (e) {
        hDirection.touchEnd(e);
    });

    cv.bind('mousedown', function (e) {
        hDirection.mouseDown(e);
    });

    cv.bind('mousemove', function (e) {
        hDirection.mouseMove(e);
    });

    cv.bind('mouseup', function (e) {
        hDirection.touchEnd(e);
    });
</script>
